/**
 * 
 */
package com.cea.papyrus.diagram.component.editpart;

import java.util.Iterator;

import org.eclipse.draw2d.XYLayout;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.gef.EditPart;
import org.eclipse.gef.commands.Command;
import org.eclipse.gef.commands.CompoundCommand;
import org.eclipse.gef.requests.ChangeBoundsRequest;
import org.eclipse.uml2.uml.Interface;
import org.eclipse.uml2.uml.Port;

import com.cea.papyrus.core.commands.Di2SemanticChildAddCommand;
import com.cea.papyrus.core.commands.GraphNodeSetConstraintCommand;
import com.cea.papyrus.core.editpart.GraphNodeEditPart;
import com.cea.papyrus.core.helper.NodeHelper;
import com.cea.papyrus.core.utils.Debug;
import com.cea.papyrus.core.utils.LookForElement;
import com.cea.papyrus.diagram.component.helper.NodePortHelper;
import com.cea.papyrus.diagraminterchange2.di2.DiagramElement;
import com.cea.papyrus.diagraminterchange2.di2.GraphEdge;
import com.cea.papyrus.diagraminterchange2.di2.GraphElement;
import com.cea.papyrus.diagraminterchange2.di2.GraphNode;
import com.cea.papyrus.diagraminterchange2.di2.Uml1SemanticModelBridge;

/**
 * @author AL090761
 *
 */
/**
 * Layout edit policy for the component diagram.
 */
public class ComponentLayoutEditPolicy extends com.cea.papyrus.diagram.composite.editpart.CompositeLayoutEditPolicy {

	/** Debug channel */
	protected static final int channel = 13;

	public ComponentLayoutEditPolicy(XYLayout layout) {
		super(layout);
	}

	/**
	 * used to create an add command.
	 * @param childEditPart the editpart that is concerned
	 * @param newConstraint to set on this editpart
	 * @param request to create an add command
	 * @return the addcommand
	 */
	protected Command createAddCommand(ChangeBoundsRequest request,
			EditPart childEditPart, 
			Object constraint) {
		CompoundCommand c= new CompoundCommand();
		GraphNode childNode = (GraphNode)childEditPart.getModel();
		//calculate the delta
		Dimension delta = new Dimension(0,0);
		delta.width = ((Rectangle)constraint).x - childNode.getPosition().x;
		delta.height = ((Rectangle)constraint).y - childNode.getPosition().y;

		//case of component
		if(childEditPart instanceof ComponentEditPart){
			GraphElement targetelement=(GraphElement)this.getHost().getModel();
			c.add(super.createAddCommand(request, childEditPart, constraint));
			AddAllSemanticChildCommand(c,request,((GraphNodeEditPart)childEditPart).getGraphNode(), delta, targetelement);
		}
		//other case
		else{
			c.add(super.createAddCommand(request, childEditPart, constraint));}
		return c;
	}

	/**
	 * construct an command to move all semantic child of a node
	 * @param request the add request
	 * @param semanticParent the node that contains semantic child
	 * @param delta the modification of position
	 * @param destination the container destination
	 * @return a set of commands
	 */
	protected Command AddAllSemanticChildCommand(CompoundCommand c, ChangeBoundsRequest request, GraphElement semanticParent, Dimension delta, GraphElement destination){

		Iterator<DiagramElement> itgraphChild= semanticParent.getSemanticChild().iterator();
		while( itgraphChild.hasNext()){

			GraphElement childNode= (GraphElement)itgraphChild.next();
			if(childNode.getSemanticChild().size()!=0){
				AddAllSemanticChildCommand(c, request, childNode, delta, destination);}
			
			//only graphNode can be added //if not the undo does not run.
			if( childNode instanceof GraphNode){
				Rectangle r= new Rectangle(childNode.getPosition().x,childNode.getPosition().y, ((GraphNode)childNode).getSize().width,((GraphNode)childNode).getSize().height);
				r.x=r.x + delta.width;
				r.y=r.y + delta.height;
				GraphNodeSetConstraintCommand locationCommand = new GraphNodeSetConstraintCommand(((GraphNode)childNode),
						request,
						r);
				c.add(locationCommand);
				Di2SemanticChildAddCommand di2addChild =new Di2SemanticChildAddCommand(childNode, destination, 
						semanticParent);
				c.add(di2addChild);
			}

		}
		return c;
	}

	/**
	 * This method is used to set a new position of the graphNode when the parent GraphNode has moved.
	 * @param parentGraphNode the parentgraphNode that have moved.
	 * @param request the change Bound Request that the parent graphNode has received.
	 * @param portGraphNode the graphNode that represents the port or the label of the GraphNode
	 * @return Rectangle that represents good position and size of the port
	 */
	@Override
	public Rectangle calculateNewPosition( GraphNode parentGraphNode, GraphNode portGraphNode, ChangeBoundsRequest request){
		Rectangle rec= new Rectangle();
		//detection of Movement
		if( request.getSizeDelta().equals(new Dimension(0,0))){

			//apply translation
			rec.setSize(portGraphNode.getSize());
			rec.setLocation(portGraphNode.getPosition());
			rec.x=rec.x+request.getMoveDelta().x;
			rec.y=rec.y+request.getMoveDelta().y;
			return rec;	
		}
		//detection of a resize
		else{

			//1 calculus of intern limit.
			Rectangle smallParentRect=new Rectangle(
					parentGraphNode.getPosition().x+request.getMoveDelta().x,
					parentGraphNode.getPosition().y+request.getMoveDelta().y,
					parentGraphNode.getSize().width+request.getSizeDelta().width,
					parentGraphNode.getSize().height+request.getSizeDelta().height
			);
			//calculus of external limit
			Rectangle bigParentRect=new Rectangle(
					parentGraphNode.getPosition().x+request.getMoveDelta().x-1,
					parentGraphNode.getPosition().y+request.getMoveDelta().y-1,
					parentGraphNode.getSize().width+request.getSizeDelta().width+2,
					parentGraphNode.getSize().height+request.getSizeDelta().height+2
			);

			//2 calculus of the middle of the port
			Point portMiddlePosition = new Point(
					portGraphNode.getPosition().x+ portGraphNode.getSize().width/2,
					portGraphNode.getPosition().y+portGraphNode.getSize().height/2
			);

			//3 if the point is in the external rectangle and not in the intern limit the port is well placed
			if ( (!smallParentRect.contains(portMiddlePosition))&& bigParentRect.contains(portMiddlePosition)){
				rec.setSize(portGraphNode.getSize());
				rec.setLocation(portGraphNode.getPosition());
				return rec;
			}
			//4 the port is badly placed
			else{
				NodePortHelper np=new NodePortHelper();
				NodeHelper nh= new NodeHelper();
				//we have to calculate position in the future position
				GraphNode futureNode=nh.createGraphElement(null, null, LookForElement.calculateAbsolutePosition(parentGraphNode,new Point(0,0)), parentGraphNode.getSize().getCopy());
				futureNode.setSize(futureNode.getSize().expand(request.getSizeDelta()));
				futureNode.setPosition(futureNode.getPosition().translate(request.getMoveDelta()));

				Debug.debug(this, "show position of future graphNode"+ futureNode.getPosition()+" "+futureNode.getSize(), channel);
				//calculate in absolute coordinate
				Point goodPosition=np.inRunner(futureNode,LookForElement.calculateAbsolutePosition(portGraphNode,new Point(0,0)), portGraphNode.getSize());
				// translate to relative
				Point origin=LookForElement.calculateAbsolutePosition(((GraphNode)parentGraphNode.getContainer()),new Point(0,0));
				goodPosition= goodPosition.getTranslated(origin.getNegated().getCopy());
				rec.setLocation(goodPosition);
				rec.setSize(portGraphNode.getSize());
				Debug.debug(this, "show new position of the port: "+rec.getLocation(), channel);
				return rec;
			}
		}
	}


	@Override
	protected Command createChangeConstraintCommand(ChangeBoundsRequest request, 
			EditPart child, Object constraint) {
		GraphNodeSetConstraintCommand locationCommand;
		CompoundCommand command= new CompoundCommand();

		//first move the concerned Editpart
		locationCommand = new GraphNodeSetConstraintCommand((GraphNode)child.getModel(), request, (Rectangle)constraint);
//		locationCommand = new GraphNodeSetConstraintCommand();
//		locationCommand.setPart((GraphNode)child.getModel());
//		locationCommand.setLocation((Rectangle)constraint);
		command.add(locationCommand);

		//if this class moves owned ports
		if(child instanceof com.cea.papyrus.diagram.component.editpart.ComponentEditPart) {

			GraphNode parentGraphNode=((com.cea.papyrus.core.editpart.GraphNodeWithSemanticEditPart)child).getGraphNode();
			// Get SemanticChildren
			// itgn iterator of graphNode (here parentgraphNode is actually componentGraphNode)
			Iterator itgn=parentGraphNode.getSemanticChild().iterator();

			while (itgn.hasNext()){
				GraphElement ge=(GraphElement)itgn.next();
				if (!(ge instanceof GraphEdge) ) {
					if (((Uml1SemanticModelBridge)ge.getSemanticModel()).getElement() instanceof Port){
						//ge is a graphNode of a port
						GraphNode portgn=(GraphNode)ge;
						// calculate new position of the port
						Rectangle rec=calculateNewPosition(
								parentGraphNode,
								portgn,
								request);
						locationCommand = new GraphNodeSetConstraintCommand(portgn, request, rec);

						// add this command in the list of commands to move Port
						command.add(locationCommand);
						//look for all portNameEditPArt graphNode
						Iterator itPortChildren=portgn.getSemanticChild().iterator();
						Dimension diff=rec.getLocation().getDifference(portgn.getPosition());
						//look for all graphNode that is connected to propertygn
						while (itPortChildren.hasNext()){

							GraphElement gelt=(GraphElement)itPortChildren.next();

							if (!(gelt instanceof GraphEdge) ) {
								GraphNode gePortChild=(GraphNode)gelt;

								//Create a move command for this element
								rec =new Rectangle(gePortChild.getPosition(),gePortChild.getSize());
								rec=rec.getTranslated(diff.width,diff.height);

								locationCommand = new GraphNodeSetConstraintCommand((GraphNode)gePortChild, request, rec);
								// add this command in the list of commands
								command.add(locationCommand);
							} 
						} 
					}
					else if (((Uml1SemanticModelBridge)ge.getSemanticModel()).getElement() instanceof Interface){

						//ge is a graphNode of an interface
						GraphNode interfacegn=(GraphNode)ge;
						// calculate new position of the interface
						Rectangle rec=calculateNewPosition(
								parentGraphNode,
								interfacegn,
								request);
						locationCommand = new GraphNodeSetConstraintCommand(interfacegn, request, rec);

						// add this command in the list of commands
						command.add(locationCommand);

						//look for all interfaceNameEditPArt graphNode
						Iterator itInterfaceChildren=interfacegn.getSemanticChild().iterator();
						Dimension diff=rec.getLocation().getDifference(interfacegn.getPosition());
						//look for all semantic children of Interface
						while (itInterfaceChildren.hasNext()){
							GraphElement gielt=(GraphElement)itInterfaceChildren.next();

							if (!(gielt instanceof GraphEdge) ) {
								//Create a move command for this element
								GraphNode geInterfaceChild=(GraphNode)gielt;
								rec =new Rectangle(geInterfaceChild.getPosition(),geInterfaceChild.getSize());
								rec=rec.getTranslated(diff.width,diff.height);

								locationCommand = new GraphNodeSetConstraintCommand((GraphNode)geInterfaceChild, request, rec);
								// add this command in the list of commands
								command.add(locationCommand);
							}
						}
					}	
				}
			}
			displayCompoundCommand(command);
			return command;
		}// end of part


		return super.createChangeConstraintCommand(request, child, constraint);
	}

	@Override
	protected Command createLabelAddCommand(ChangeBoundsRequest request,GraphNode graphNode,Object constraint) {
		CompoundCommand cmd = new CompoundCommand();
		if( graphNode.getSemanticParent()!=null &&
				((Uml1SemanticModelBridge)((GraphNode)graphNode.getSemanticParent()).getSemanticModel()).getElement() instanceof Port){

			GraphNodeSetConstraintCommand locationCommand = new GraphNodeSetConstraintCommand(graphNode, request, (Rectangle)constraint);
			cmd.add(locationCommand);}
		// graphElements that represent label don't have to move another graphElement

		else 
		{cmd.add(super.createLabelAddCommand(request, graphNode, constraint));}
		return cmd;
	}
}